package com.study.example.gameserver.module.scene.manager;

import com.sparrow.common.ai.behaviortree.BehaviorContext;
import com.sparrow.common.ai.behaviortree.factory.BehaviorTreeFactory;
import com.sparrow.common.ai.behaviortree.node.BehaviorNode;
import com.sparrow.common.ai.behaviortree.task.BehaviorTask;
import com.sparrow.common.aoi.domain.MapData;
import com.sparrow.common.aoi.util.MapUtil;
import com.sparrow.common.thread.ThreadPool;
import com.sparrow.common.tick.GameLoop;
import com.sparrow.common.tick.def.Tickable;
import com.sparrow.common.util.FileUtil;
import com.sparrow.common.uuid.TimeUUIDGenerator;
import com.sparrow.event.annotation.EventReceiver;
import com.sparrow.resource.cache.ResourceCache;
import com.sparrow.common.map.Position;
import com.study.example.gameserver.config.MonsterResource;
import com.study.example.gameserver.config.SceneResource;
import com.study.example.gameserver.event.ServerInitEvent;
import com.study.example.gameserver.module.scene.bt.monitor.DebugBTMonitor;
import com.study.example.gameserver.module.scene.domain.MapMonsterSpawnData;
import com.study.example.gameserver.module.scene.domain.MonsterSpawnPoint;
import com.study.example.gameserver.module.scene.domain.SceneMap;
import com.study.example.gameserver.module.scene.entity.IEntity;
import com.study.example.gameserver.module.scene.entity.impl.Monster;
import com.study.example.gameserver.module.scene.service.SceneService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;

@Component
public class SceneManager implements Tickable {

    private static final Logger logger = LoggerFactory.getLogger(SceneManager.class);

    @Autowired
    private ResourceCache<Integer, SceneResource> sceneResCache;
    @Autowired
    private ResourceCache<Integer, MonsterResource> monsterResCache;
    @Autowired
    private TimeUUIDGenerator uuidGenerator;
    @Autowired
    private SceneService sceneService;
    @Autowired
    private GameLoop gameLoop;
    @Autowired
    private DebugBTMonitor btMonitor;

    private final Map<Integer, SceneMap> sceneMaps = new ConcurrentHashMap<>();
    private final Map<String, BehaviorNode> behaviorTreeCache = new ConcurrentHashMap<>();

    @EventReceiver
    public void init(ServerInitEvent event) {
        for (SceneResource sceneResource : sceneResCache.getAllResource()) {
            create(sceneResource);
        }
        gameLoop.addTickable(this);
    }

    private void create(SceneResource sceneResource) {
        int lineNum = sceneResource.getLineNum();
        for (int i = 0; i < lineNum; i++) {
            int lineId = i + 1;
            int mapId = MapUtil.toMapId(sceneResource.getId(), lineId);
            MapMonsterSpawnData spawnData = MapMonsterSpawnData.parse(sceneResource.getMonsterSpawn());
            MapData mapData = MapData.loadFromFile(sceneResource.getMapDataName(), sceneResource.getAreaWidth());
            SceneMap sceneMap = new SceneMap(sceneResource.getId(), lineId, mapId, mapData, ThreadPool.SCENE_EXECUTOR);
            if (sceneResource.getMonsterSpawn() != null) {
                initMonsters(sceneMap, spawnData);
            }
            sceneMap.getAoiSystem().addListener(sceneService);
            sceneMaps.put(mapId, sceneMap);

            logger.info("创建场景 场景实例id={} 场景配置id={} width:{} height:{} areaSize:{}",
                    mapId, sceneResource.getId(),
                    mapData.getWidth(), mapData.getHeight(),
                    sceneMap.getAreaMap().size());
        }
    }

    private void initMonsters(SceneMap sceneMap, MapMonsterSpawnData spawnData) {
        for (MonsterSpawnPoint spawn : spawnData.getList()) {
            addMonster(sceneMap, spawn.getMonsterType(), spawn.getPosition());
        }
    }

    public void addMonster(SceneMap sceneMap, int configId, Position spawn) {
        Monster monster = createMonster(sceneMap, configId, spawn);
        if (monster != null) {
            sceneMap.addMonster(monster);
        }
    }

    public Monster createMonster(SceneMap sceneMap, int configId, Position spawn) {
        MonsterResource res = monsterResCache.getResource(configId);
        if (res == null) return null;
        Monster monster = new Monster();
        monster.setId(uuidGenerator.nextId());
        monster.setConfigId(configId);
        monster.setPos(Position.copyOf(spawn));
        monster.setAOIX((int) monster.getPos().getX());
        monster.setAOIY((int) monster.getPos().getZ());
        monster.setHp(res.getHp());
        monster.setMaxHp(res.getHp());
        monster.setAtk(res.getAtk());
        monster.setSpawn(Position.copyOf(spawn));
        monster.setSpeed(res.getSpeed());
        monster.setSceneId(sceneMap.getSceneId());
        monster.setLineId(sceneMap.getLineId());
        monster.setMapId(sceneMap.getMapId());
        monster.setAngle(ThreadLocalRandom.current().nextInt(360));

        String ai = res.getAi();
        if (ai != null && !ai.isEmpty()) {
            try {
                BehaviorNode node = behaviorTreeCache.computeIfAbsent(ai, k -> {
                    File xmlFile = FileUtil.getFile("config/ai/" + k + ".xml");
                    return BehaviorTreeFactory.buildFromXml(xmlFile);
                });

                BehaviorTask task = node.createTask();
                BehaviorContext context = new BehaviorContext();
                context.put("SceneMap", sceneMap);
                context.put("Self", monster);
                context.put("SceneMapService", sceneService);
                context.setRootTask(task);
                context.setOwner(monster);
                context.setMonitor(btMonitor);

                monster.setBehaviorContext(context);
            } catch (Exception e) {
                logger.error("初始化怪物行为树失败 ai={}", ai, e);
            }
        }

        return monster;
    }

    public SceneMap getSceneMap(int sceneId, int lineId) {
        return getSceneMap(MapUtil.toMapId(sceneId, lineId));
    }

    public SceneMap getSceneMap(int mapId) {
        return sceneMaps.get(mapId);
    }

    public SceneMap getSceneMap(IEntity e) {
        return getSceneMap(e.getMapId());
    }

    @Override
    public void tick(long now, long delta) {
        sceneMaps.values().forEach(sceneMap ->
                sceneMap.getMonsters().values().forEach(monster -> {
                    BehaviorContext ctx = monster.getBehaviorContext();
                    if (ctx != null) {
                        ctx.getRootTask().tick(ctx, now, delta);
                    }
                })
        );
    }
}
